class: center, middle, inverse, title-slide # Geospatial Techniques for Social Scientists in R ## Advanced Data Import ### Stefan Jünger & Anne-Kathrin Stroppe
February 09, 2021 --- layout: true --- ## Now <table class="table" style="margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;"> Day </th> <th style="text-align:left;"> Time </th> <th style="text-align:left;"> Title </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;color: gray !important;"> February 08 </td> <td style="text-align:left;color: gray !important;"> 09:00am-10:30am </td> <td style="text-align:left;font-weight: bold;"> Introduction </td> </tr> <tr> <td style="text-align:left;color: gray !important;"> February 08 </td> <td style="text-align:left;color: gray !important;"> 11:00am-12:30pm </td> <td style="text-align:left;font-weight: bold;"> Vector Data </td> </tr> <tr> <td style="text-align:left;color: gray !important;color: gray !important;"> February 08 </td> <td style="text-align:left;color: gray !important;color: gray !important;"> 12:30pm-01:30pm </td> <td style="text-align:left;font-weight: bold;color: gray !important;"> Lunch Break </td> </tr> <tr> <td style="text-align:left;color: gray !important;"> February 08 </td> <td style="text-align:left;color: gray !important;"> 01:30pm-03:00pm </td> <td style="text-align:left;font-weight: bold;"> Basic Maps </td> </tr> <tr> <td style="text-align:left;color: gray !important;border-bottom: 1px solid"> February 08 </td> <td style="text-align:left;color: gray !important;border-bottom: 1px solid"> 03:30pm-05:00pm </td> <td style="text-align:left;font-weight: bold;border-bottom: 1px solid"> Raster Data </td> </tr> <tr> <td style="text-align:left;color: gray !important;background-color: yellow !important;"> February 09 </td> <td style="text-align:left;color: gray !important;background-color: yellow !important;"> 09:00am-10:30am </td> <td style="text-align:left;font-weight: bold;background-color: yellow !important;"> Advanced Data Import </td> </tr> <tr> <td style="text-align:left;color: gray !important;"> February 09 </td> <td style="text-align:left;color: gray !important;"> 11:00am-12:30pm </td> <td style="text-align:left;font-weight: bold;"> Applied Data Wrangling </td> </tr> <tr> <td style="text-align:left;color: gray !important;color: gray !important;"> February 09 </td> <td style="text-align:left;color: gray !important;color: gray !important;"> 12:30pm-13:30pm </td> <td style="text-align:left;font-weight: bold;color: gray !important;"> Lunch Break </td> </tr> <tr> <td style="text-align:left;color: gray !important;"> February 09 </td> <td style="text-align:left;color: gray !important;"> 01:30pm-03:00pm </td> <td style="text-align:left;font-weight: bold;"> Advanced Maps I </td> </tr> <tr> <td style="text-align:left;color: gray !important;"> February 09 </td> <td style="text-align:left;color: gray !important;"> 03:30pm-05:00pm </td> <td style="text-align:left;font-weight: bold;"> Advanced Maps II </td> </tr> </tbody> </table> --- ## `R`-Packages *we* will use - `osmdata` - `tmaptools` - `reticulate` - `StefanJuenger/z11` --- ## More on the geospatial data landscape Geospatial data tend to be quite big - pressure to distribute data efficiently Data dumps (on the internet) may not be helpful when resources are low and/or time's a factor Already for a long time, there are standards to distribute data over the internet and make accessing only chunks of them possible - Use of *Programming Application Interfaces* (API) --- ## What is an API? An Application Programming Interface (API) serves as an entry point to data distributed over the internet to - get data - push data Standardized mechanisms - to query data - often just a simple text string to enter in your URL-address bar - it gets complicated when login data are required --- ## Data Providers Offering Geospatial Data APIs - OpenStreetMap - Google - Bing - ... - Cologne's Open Data Portal - --- ## How are data accessed? 1. (What is there to get? 2. (Can I get it?) 3. Specify what I can get 3. Get it! --- ## Example: Access of Public Transport Say, we're interested in the accessibility of public transport in Cologne - bus, tram, etc. - all platforms and vehicles should be wheel-chair accessible **We can gather this information using OpenStreetMap!** --- ## Accessing OSM Data: the Overpass API > The Overpass API (formerly known as OSM Server Side Scripting, or OSM3S before 2011) is a read-only API that serves up custom selected parts of the OSM map data. It acts as a database over the web: the client sends a query to the API and gets back the data set that corresponds to the query. .tinyisher[Source: https://wiki.openstreetmap.org/wiki/Overpass_API] --- ## Starting with a Geographic Area to Query Many geospatial API requests start with a bounding box or another geographical extent to define from which area should be accessed. .pull-left[ ```r cologne_pt_stops <- osmdata::getbb( "Köln", format_out = "sf_polygon" ) ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/cologne-polygon-plot-1.png" style="display: block; margin: auto;" /> ] --- ## Defining some technical details The Overpass API in particular also requires a timeout parameter that repeats the request a certain amount of time if anything fails. ```r cologne_pt_stops <- cologne_pt_stops %>% osmdata::opq(timeout = 25*100) cologne_pt_stops ``` ``` ## $bbox ## [1] "50.8304399,6.7725303,51.0849743,7.162028" ## ## $prefix ## [1] "[out:xml][timeout:2500];\n(\n" ## ## $suffix ## [1] ");\n(._;>;);\nout body;" ## ## $features ## NULL ## ## attr(,"class") ## [1] "list" "overpass_query" ## attr(,"nodes_only") ## [1] FALSE ``` --- ## Turning to the content The content which we aim to request is defined with key/value pairs. It's best to learn them by doing and look them up in the [official documentation](https://wiki.openstreetmap.org/wiki/Map_features). ```r cologne_pt_stops <- cologne_pt_stops %>% osmdata::add_osm_feature(key = "public_transport", value = "stop_position") cologne_pt_stops ``` ``` ## $bbox ## [1] "50.8304399,6.7725303,51.0849743,7.162028" ## ## $prefix ## [1] "[out:xml][timeout:2500];\n(\n" ## ## $suffix ## [1] ");\n(._;>;);\nout body;" ## ## $features ## [1] " [\"public_transport\"=\"stop_position\"]" ## ## attr(,"class") ## [1] "list" "overpass_query" ## attr(,"nodes_only") ## [1] FALSE ``` --- ## Conduct actual request/download In the `osmdata` package, data is actually downloaded, e.g., by using the `osmdata::osmdata_sf()` function. ```r cologne_pt_stops <- cologne_pt_stops %>% osmdata::osmdata_sf() cologne_pt_stops ``` --- ## Filter and transform The data comprises a list that can be accessed as any list in `R`. Here we extract the points and wrangle them spatially. .pull-left[ ```r cologne_pt_stops <- cologne_pt_stops %>% .$osm_points %>% tibble::as_tibble() %>% sf::st_as_sf() %>% sf::st_transform(3035) %>% dplyr::filter(wheelchair == "yes") cologne_pt_stops ``` ``` ## Simple feature collection with 596 features and 78 fields ## geometry type: POINT ## dimension: XY ## bbox: xmin: 4094554 ymin: 3084872 xmax: 4121662 ymax: 3112024 ## projected CRS: ETRS89-extended / LAEA Europe ## # A tibble: 596 x 79 ## osm_id name FIXME VRS.gemeinde VRS.name VRS.ortsteil VRS.ref access alt_name ## * <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> ## 1 361716 Eife~ <NA> KÖLN <NA> Innenstadt 11509 <NA> <NA> ## 2 388550 Brah~ <NA> KÖLN <NA> Lindenthal 13313 <NA> <NA> ## 3 21033~ Wein~ <NA> KÖLN Weinsbe~ Ehrenfeld 14221 <NA> <NA> ## 4 27042~ Markt <NA> BERGISCH GL~ Bergisc~ Mitte 31241 <NA> <NA> ## 5 28122~ Heum~ <NA> KÖLN <NA> Innenstadt 11110 <NA> <NA> ## 6 28301~ Kalk~ <NA> KÖLN Kalker ~ Merheim 18510 <NA> <NA> ## 7 28301~ Merh~ <NA> KÖLN <NA> Merheim 18511 <NA> <NA> ## 8 28301~ Refr~ <NA> BERGISCH GL~ <NA> Refrath 31612 <NA> <NA> ## 9 30145~ Köl~ <NA> KÖLN Holweid~ Holweide 19401 <NA> <NA> ## 10 30388~ Mül~ <NA> <NA> <NA> <NA> <NA> <NA> <NA> ## # ... with 586 more rows, and 70 more variables: amenity <chr>, barrier <chr>, ## # bench <chr>, bin <chr>, bus <chr>, bus_bay <chr>, bus_lines <chr>, ## # covered <chr>, crossing <chr>, departures_board <chr>, description <chr>, ## # direction <chr>, disused <chr>, fixme <chr>, highway <chr>, ## # internet_access <chr>, layer <chr>, level <chr>, light_rail <chr>, ## # local_ref <chr>, name.ar <chr>, name.en <chr>, network <chr>, ## # network.short <chr>, note <chr>, note_2 <chr>, old_name <chr>, ## # operator <chr>, operator.short <chr>, passenger_information_display <chr>, ## # platform <chr>, pole <chr>, pole.check_date <chr>, proposed <chr>, ## # public_transport <chr>, railway <chr>, railway.ref <chr>, ## # railway.ref.parent <chr>, ref <chr>, ref.AST <chr>, ref.IFOPT <chr>, ## # ref.VRS <chr>, ref.hafas <chr>, ref.ibnr <chr>, ref_name <chr>, ## # reg_name <chr>, route_ref <chr>, route_ref.note <chr>, share_taxi <chr>, ## # shared_taxi <chr>, shelter <chr>, shelter.check_date <chr>, source <chr>, ## # source.position <chr>, start_date <chr>, station <chr>, subway <chr>, ## # swing_gate.type <chr>, tactile_paving <chr>, toilets.wheelchair <chr>, ## # traffic_sign <chr>, train <chr>, tram <chr>, uic_ref <chr>, website <chr>, ## # wheelchair <chr>, wheelchair.description <chr>, wikidata <chr>, ## # wikipedia <chr>, geometry <POINT [m]> ``` ] --- ## The data indeed are mappable .pull-left[ ```r tm_shape(cologne_pt_stops) + tm_dots() ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/cologne-pt-stops-map-1.png" style="display: block; margin: auto;" /> ] --- ## Fiddling with the data: Creating a quick 'heatmap' OpenStreetMap points data are nice to analyze urban infrastructure. Let's draw a quick 'heatmap' using kernel densities. .pull-left[ ```r cologne_pt_stops_densities <- cologne_pt_stops %>% sf::as_Spatial() %>% as("ppp") %>% spatstat::density.ppp(sigma = 500) %>% as.data.frame() %>% raster::rasterFromXYZ(crs = 3035) ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/cologne-pt-stops-densities-plot-1.png" style="display: block; margin: auto;" /> ] --- class: middle ## Exercise 2_1_1: Working with OSM Data [Exercise](https://stefanjuenger.github.io/gesis-workshop-geospatial-techniques-R/exercises/2_1_1_Working_with_OSM_data_question.html) [Solution](https://stefanjuenger.github.io/gesis-workshop-geospatial-techniques-R/exercises/2_1_1_Working_with_OSM_data_solution.html) --- ## Accessing unpackaged (vector) data Not all data come as pretty as OpenStreetMap data in, e.g., the `osmdata` package. Don't worry, there are methods to import data from source that - only provide an URL - not yet prepared for analysis --- ## Example: GeoJSON files JSON files are pretty popular - standardized and well structured - similar to XML-files, but human readibilty is a bit better - also easy to parse for editors and browser There's also a JSON flavor for geospatial data... --- ## GeoJSON snippet ``` { "type": "FeatureCollection", "features": [ { "type": "Feature", "id": 12, "geometry": { "type": "Polygon", "coordinates": [ [ [ 6.957362270020273, 50.94308762750329 ] ... ``` .tinyisher[Source: https://www.offenedaten-koeln.de/] --- ## An Application from Cologne's open data portal ```r where_to_wear <- glue::glue( "https://geoportal.stadt-koeln.de/arcgis/rest/services/\\ Politik_und_Verwaltung/maskenpflicht/MapServer/0/query?\\ where=id+is+not+null&text=&objectIds=&time=&geometry=&geometryType=\\ esriGeometryEnvelope&inSR=&spatialRel=esriSpatialRelIntersects&distance=\\ &units=esriSRUnit_Foot&relationParam=&outFields=*&returnGeometry=true&\\ returnTrueCurves=false&maxAllowableOffset=&geometryPrecision=&outSR=4326&\\ havingClause=&returnIdsOnly=false&returnCountOnly=false&orderByFields=&\\ groupByFieldsForStatistics=&outStatistics=&returnZ=false&returnM=false&\\ gdbVersion=&historicMoment=&returnDistinctValues=false&resultOffset=&\\ resultRecordCount=&returnExtentOnly=false&datumTransformation=&\\ parameterValues=&rangeValues=&quantizationParameters=&featureEncoding=\\ esriDefault&f=geojson" ) %>% sf::st_read(as_tibble = TRUE) ``` ``` ## Reading layer `OGRGeoJSON' from data source `https://geoportal.stadt-koeln.de/arcgis/rest/services/Politik_und_Verwaltung/maskenpflicht/MapServer/0/query?where=id+is+not+null&text=&objectIds=&time=&geometry=&geometryType=esriGeometryEnvelope&inSR=&spatialRel=esriSpatialRelIntersects&distance=&units=esriSRUnit_Foot&relationParam=&outFields=*&returnGeometry=true&returnTrueCurves=false&maxAllowableOffset=&geometryPrecision=&outSR=4326&havingClause=&returnIdsOnly=false&returnCountOnly=false&orderByFields=&groupByFieldsForStatistics=&outStatistics=&returnZ=false&returnM=false&gdbVersion=&historicMoment=&returnDistinctValues=false&resultOffset=&resultRecordCount=&returnExtentOnly=false&datumTransformation=¶meterValues=&rangeValues=&quantizationParameters=&featureEncoding=esriDefault&f=geojson' using driver `GeoJSON' ## Simple feature collection with 34 features and 3 fields ## geometry type: POLYGON ## dimension: XY ## bbox: xmin: 6.8943 ymin: 50.88276 xmax: 7.075354 ymax: 50.9786 ## geographic CRS: WGS 84 ``` .tinyisher[Source: https://www.offenedaten-koeln.de/] --- ## Where to weark a mask We now know where in Cologne you should wear a mask using `R`. Not too bad, right? .pull-left[ ```r tm_shape(where_to_wear) + tm_polygons(col = "red", alpha = .75) ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/where-to-wear-plot-exec-1.png" style="display: block; margin: auto;" /> ] --- ## Raster data access APIs are not only used for vector data, but also for raster data. The idea is the same - accessing information on data through URLs - just the downloaded data formats differ --- ## OSM data can be gathered as raster data, too The `tmaptools` package is pretty handy to download OpenStreetMap data (tiles). ```r cologne_raster <- tmaptools::read_osm(where_to_wear) %>% # it's a stars object as("Raster") cologne_raster ``` ``` ## class : RasterBrick ## dimensions : 441, 525, 231525, 3 (nrow, ncol, ncell, nlayers) ## resolution : 38.39006, 38.3844 (x, y) ## extent : 767470, 787624.8, 6600582, 6617510 (xmin, xmax, ymin, ymax) ## crs : +proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs ## source : memory ## names : red, green, blue ## min values : 0, 0, 0 ## max values : 254, 254, 254 ``` --- ## Mapped OSM raster data The resulting data can be packed in a `tmap` workflow. .pull-left[ ```r tm_shape(cologne_raster) + tm_rgb() ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/cologne-raster-map-exec-1.png" style="display: block; margin: auto;" /> ] --- ## Use it as background map As these data are basically images, they are perfect to be used as background maps when mapping other geospatial attributes, such as our mask wearing data. .pull-left[ ```r tm_shape(cologne_raster) + tm_rgb() + tm_shape(where_to_wear) + tm_polygons(col = "red", alpha = .75) ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/cologne-raster-map-bg-exec-1.png" style="display: block; margin: auto;" /> ] --- ## Playing with different map types List of available type names can be seen with the function call `OpenStreetMap::getMapInfo()`. -- .pull-left[ ```r tmaptools::read_osm( where_to_wear, type = "esri-topo" ) %>% tm_shape() + tm_rgb() + tm_shape(where_to_wear) + tm_polygons(col = "red", alpha = .75) ``` <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/esri-topo-1.png" width="70%" style="display: block; margin: auto;" /> ] -- .pull-right[ ```r tmaptools::read_osm( where_to_wear, type = "stamen-watercolor" ) %>% tm_shape() + tm_rgb() + tm_shape(where_to_wear) + tm_polygons(col = "red", alpha = .75) ``` <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/stamen-watercolor-1.png" width="70%" style="display: block; margin: auto;" /> ] --- ## OGC Web Services OSM and other provide their own standardized interface to their data. The Open Geospatial Consortium developed a more broadly used interface design to more diverse data sources - often used by public authorities all over the world - support of well-supported data formats - good documentation --- ## Displaying vs. downloading These web services can broadly be divided into services to - display data - download data Let's briefly focus on download services! --- ## Download Services Web Feature Service (WFS) - vector data **Web Coverage Service (WCS)** - raster data Unfortunately, as of today there are no ready-to-play packages providing full access to OWS services in `R` - The [`ows4R` package](https://cran.r-project.org/web/packages/ows4R/index.html) let's you only use WFS services - I (Stefan), however, work a lot with raster data In the following, we will see a way establishing an interface to `Python` and using its `OWSLib` to access WCS data. --- ## Monitor of Settlement and Open Space Development (IOER Monitor) .pull-left[ The Leibniz Institute of Ecological Urban and Regional Development provides the [Monitor of Settlement and Open Space Development (IOER Monitor)](https://www.ioer-monitor.de/en/) - information on land use in Germany - over time - small scale raster resolutions available (min: 100m `\(\times\)` 100m) ] .pull-right[ <img src="data:image/png;base64,#./img/fig_linking_buffer_sealing.png" width="75%" style="display: block; margin: auto;" /> ] --- ## Function Accessing Python's `OWSLib` .pull-left[ .mini[ ```r #' Retrieve Raster Layer from the IOER Monitor #' Download raster layer from the Monitor of Settlement and Open Space #' Development (IOER Monitor) and load it in your R session. #' #' @param indicator_key character string; indicator key as defined here: #' \url{https://www.ioer-monitor.de/en/indicators/} #' @param size character string; combination of size and unit, e.g., #' \code{"500m"}. ATTENTION: sizes below 200m are currently not working :-( #' @param year character string; reference year for the indicator. ATTENTION: #' not all indicators are available for any year #' @param tmp_folder character string; path where the downloaded and temporary #' file should be stored to reload as raster layer #' #' @importFrom magrittr %>% #' @importFrom reticulate %as% download_ioer_layer <- function ( indicator_key = "S08RG", size = "500m", year = "2019", tmp_folder = "." ) { # create interface to python py <- reticulate::import_builtins() # build web coverage service request indicator_wcs <- paste0("http://maps.ioer.de/cgi-bin/wcs?MAP=", indicator_key, "_wcs") %>% reticulate::import("owslib.wcs")$WebCoverageService(., version = '1.0.0') # retrieve layer name layer_name <- indicator_wcs$contents %>% names() %>% grep(year, ., value = TRUE) %>% grep(size, ., value = TRUE) # identify the actual layer to download, ... layer_to_download <- layer_name %>% indicator_wcs[.] # ...its resolution, ... layer_resolution <- layer_to_download %>% .$grid %>% .$highlimits %>% as.numeric() ... ``` ] ] .pull-right[ .mini[ ```r ... # ...its native coordinate reference system, ... layer_native_crs <- layer_to_download %>% .$boundingboxes %>% .[2] %>% .[[1]] %>% .$nativeSrs # ...and its bounding box layer_bounding_box <- layer_to_download %>% .$boundingboxes %>% .[2] %>% .[[1]] %>% .$bbox %>% unlist() # download layer downloaded_layer <- indicator_wcs$getCoverage( identifier = layer_name, bbox = layer_bounding_box, format = "GTiff", crs = layer_native_crs, width = layer_resolution[1], height = layer_resolution[2] ) %>% .$read %>% reticulate::py_call(.) # store tmp file with(py$open( paste0(tmp_folder, '/layer.tif'), "wb") %as% file, { file$write(downloaded_layer) } ) # load tmp file as 'native' r raster file raster_layer <- raster::raster(paste0(tmp_folder, '/layer.tif')) raster_layer <- raster::readAll(raster_layer) # delete tmp file unlink(paste0(tmp_folder, '/layer.tif')) # return raster layer raster_layer } ``` ] ] --- ## Green Spaces We can use this function, e.g., for downloading the green space distribution in Cologne. .pull-left[ ```r source("../../R/download_ioer_layer.R") cologne <- osmdata::getbb( "Köln", format_out = "sf_polygon" ) %>% sf::st_transform(3035) cologne_green <- download_ioer_layer( indicator_key = "S08RG", size = "500m", year = "2019", tmp_folder = "." ) %>% raster::crop(cologne) %>% { mannheim_green_for_linking <<- . } %>% raster::rasterToPolygons() %>% sf::st_as_sf() %>% sf::st_transform(3035) %>% sf::st_intersection(cologne) ``` ] -- .pull-right[ ```r tm_shape(cologne_green) + tm_polygons("layer", palette = "Greens") ``` <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/map-ioer-green-execution-1.png" style="display: block; margin: auto;" /> ] --- ## Shameless advertising: the `z11` package There are times where no APIs are available for downloading geospatial data. The German Census 2011 is a prime example - only a data dump of Gigabytes of data exists for hundrends of attributes - when you often work with these data, it's a pain **Thus, I created my own (pseudo-)API for these data hosted over Github.** --- ## Accessing data Details on using the package can be found [here](https://stefanjuenger.github.io/z11/articles/using-z11.html), but it's really easy to use. For example, if you want to download data on immigrant rates on a 1 km² grid you can use the following function. ```r immigrants_germany <- z11::z11_get_1km_attribute(Auslaender_A) immigrants_germany[immigrants_germany <= -1] <- NA immigrants_germany ``` ``` ## class : RasterLayer ## dimensions : 867, 641, 555747 (nrow, ncol, ncell) ## resolution : 1000, 1000 (x, y) ## extent : 4031500, 4672500, 2684500, 3551500 (xmin, xmax, ymin, ymax) ## crs : +proj=laea +lat_0=52 +lon_0=10 +x_0=4321000 +y_0=3210000 +ellps=GRS80 +units=m +no_defs ## source : memory ## names : layer ## values : 0, 100 (min, max) ``` --- ## It's a raster As it is a raster file, you can plot it fairly easy. -- .pull-left[ ```r tm_shape(immigrants_germany) + tm_raster(palette = "viridis") ``` ] -- .pull-right[ <img src="data:image/png;base64,#2_1_Advanced_Data_Import_files/figure-html/census-immigrants-map-execution-1.png" style="display: block; margin: auto;" /> ] --- class: middle ## Exercise 2_1_2: Wrangling the German Census [Exercise](https://stefanjuenger.github.io/gesis-workshop-geospatial-techniques-R/exercises/2_1_2_Wrangling_the_German_Census_question.html) [Solution](https://stefanjuenger.github.io/gesis-workshop-geospatial-techniques-R/exercises/2_1_2_Wrangling_the_German_Census_solution.html) --- class: middle ## Break --- layout: false class: center background-image: url(data:image/png;base64,#./img/the_end.png) background-size: cover .left-column[ </br> <img src="data:image/png;base64,#./img/stefan.png" width="90%" style="display: block; margin: auto;" /> ] .right-column[ .left[.small[<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm0 48v40.805c-22.422 18.259-58.168 46.651-134.587 106.49-16.841 13.247-50.201 45.072-73.413 44.701-23.208.375-56.579-31.459-73.413-44.701C106.18 199.465 70.425 171.067 48 152.805V112h416zM48 400V214.398c22.914 18.251 55.409 43.862 104.938 82.646 21.857 17.205 60.134 55.186 103.062 54.955 42.717.231 80.509-37.199 103.053-54.947 49.528-38.783 82.032-64.401 104.947-82.653V400H48z"></path></svg> [`stefan.juenger@gesis.org`](mailto:cute.cat@gesis.org)] </br> .small[<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg> [`@StefanJuenger`](https://twitter.com/CuteCat)] </br> .small[<svg viewBox="0 0 496 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg> [`StefanJuenger`](https://github.com/cute_cat)] </br> .small[<svg viewBox="0 0 576 512" xmlns="http://www.w3.org/2000/svg" style="height:1em;fill:currentColor;position:relative;display:inline-block;top:.1em;"> [ comment ] <path d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z"></path></svg> [`https://stefanjuenger.github.io`](https://www.cute-cat.org)]] </br> ]